In this notebook we are comparing the use of Alevin-fry and Spaceranger for quantifying spatial transcriptomics libraries. Two spatial transcriptomic libraries were quantified using Alevin-fry and Spaceranger and the results were combined following the Alevin-fry tutorial. We are comparing that to if we were to not integrate the Spaceranger data with Alevin-fry and only use Spaceranger.

Additionally in this notebook we also explore the use of storing spatial data in a SpatialExperiment object or in a Seurat object.

Set Up

library(magrittr)
library(ggplot2)
library(SingleCellExperiment)
library(SpatialExperiment)
library(ggspavis)
Registered S3 method overwritten by 'ggside':
  method from   
  +.gg   ggplot2
library(ggupset)
# load in benchmarking functions that will be used for copying data and generating sample tables
function_path <- file.path(".." ,"benchmarking-functions", "R")
file.path(function_path, list.files(function_path, pattern = "*.R$")) %>%
  purrr::walk(source)
# set up file paths 
base_dir <- here::here()

# output folder to store alevin-fry and cellranger quants from S3
data_dir <- file.path(base_dir, "data", "spatial") 
s3_out <- file.path(data_dir, "data", "quants")

# output folder for image files 
image_dir <- file.path(data_dir, "data", "images")

# results directory 
results_dir <- file.path(data_dir, "results")

# create directories
if(!dir.exists(data_dir)){
  dir.create(data_dir, recursive = TRUE)
}

if(!dir.exists(image_dir)){
  dir.create(image_dir, recursive = TRUE)
}

if(!dir.exists(results_dir)){
  dir.create(results_dir, recursive = TRUE)
}
mito_file <- file.path(base_dir, "sample-info", "Homo_sapiens.GRCh38.103.mitogenes.txt")
  
# read in mito genes 
mito_genes <- readr::read_tsv(mito_file, col_names = "gene_id")
Rows: 111 Columns: 1
── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (1): gene_id

β„Ή Use `spec()` to retrieve the full column specification for this data.
β„Ή Specify the column types or set `show_col_types = FALSE` to quiet this message.
mito_genes <- mito_genes %>%
  dplyr::pull(gene_id) %>%
  unique()

Copy data from S3

Comparing SpatialExperiment to Seurat

Importing Alevin-fry and Spaceranger output into a SpatialExperiment

First we are testing use of a SpatialExperiment object to store the spatial transcriptomics results. We will create two functions, one to deal with creating the combined object from both Alevin-fry and Spaceranger output, and the second to import Spaceranger output only.

# custom function for converting Alevin-fry and Spaceranger output to spe
convert_fry_spaceranger_spe <- function(fry_dir, spaceranger_dir, sample_name){
  fry_sce <- scpcaTools::read_alevin(fry_dir, 
                                     usa_mode = TRUE,
                                     which_counts = "spliced")
  
  # read in image data
  image_data <- SpatialExperiment::readImgData(spaceranger_dir,
                            sample_id = sample_name)
  
  # read in spatial coordinates 
  spatial_data_file <- file.path(spaceranger_dir, "tissue_positions_list.csv")
  spatial_data <- readr::read_csv(spatial_data_file, col_names = c("barcode", "in_tissue", "array_row",
                                                                   "array_col","pxl_row_in_fullres",
                                                                   "pxl_col_in_fullres"))
  # find common cells between Alevin-fry and spaceranger 
  spatial_data <- spatial_data %>%
    dplyr::mutate(barcode = gsub("-1", "", barcode))
  common_cells <- intersect(colnames(fry_sce), spatial_data$barcode)
  
  # subset fry_sce by common cells
  fry_sce_subset <- fry_sce[,common_cells]
  spatial_data_subset <- spatial_data %>%
    dplyr::filter(barcode %in% common_cells)
  
  # construct 'SpatialExperiment'
  fry_spe <- SpatialExperiment(
    assays = list(counts = assay(fry_sce_subset)),
    colData = colData(fry_sce_subset), 
    imgData = image_data,
    spatialData = DataFrame(spatial_data_subset),
    spatialCoordsNames = c("pxl_col_in_fullres", "pxl_row_in_fullres"),
    sample_id = sample_name)
}
spaceranger_only_spe <- function(spaceranger_outs_dir, sample_name){
  # read in spaceranger outputs directly using read10XVisium
  spaceranger_spe <- read10xVisium(spaceranger_outs_dir,
                                 sample_id = sample_name,
                                 type = "sparse",
                                 data = "raw",
                                 images = "lowres", 
                                 load = FALSE)
}

Let’s test these functions out and read in the Alevin-fry and Spaceranger output for one sample.

# get path to fry output directory 
fry_dir <- file.path(s3_out, "alevin-fry-knee", sample_ids[1])
fry_dir <- paste0(fry_dir, "-spliced_intron_txome_k31-salign-cr-like-em-knee")

# paths to spatial folders 
cellranger_folders <- paste0(sample_ids, "-cdna-spatial")
spatial_dir <- file.path(s3_out, "cellranger", cellranger_folders[1], "outs", "spatial")
# read in combined fry and spaceranger spe 
fry_spe <- convert_fry_spaceranger_spe(fry_dir, 
                                       spatial_dir, 
                                       sample_ids[1])
Rows: 4992 Columns: 6
── Column specification ──────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): barcode
dbl (5): in_tissue, array_row, array_col, pxl_row_in_fullres, pxl_col_in_fullres

β„Ή Use `spec()` to retrieve the full column specification for this data.
β„Ή Specify the column types or set `show_col_types = FALSE` to quiet this message.
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’

Below we import just the Spaceranger outputs into a SpatialExperiment object.

# spaceranger output paths
spaceranger_out <- file.path(s3_out, "cellranger", cellranger_folders[1], "outs")

# read in spaceranger output directly using read10XVisium
spaceranger_spe <- spaceranger_only_spe(spaceranger_out, sample_ids[1])
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’

Importing as Seurat Object

Below we will import the same data but as a Seurat object instead of a SpatialExperiment object.

# first create sce from Alevin output
fry_sce <- scpcaTools::read_alevin(fry_dir,
                                   usa_mode = TRUE,
                                   which_counts = "spliced")

# convert sce to seurat object 
fry_seurat <- CreateSeuratObject(counts = counts(fry_sce),
                                 project = "SPATIAL",
                                 assay = "Spatial")

# read in image data using Seurat and subset to common cells
image_data <- Read10X_Image(spatial_dir)
rownames(image_data@coordinates) <- gsub("-1","", rownames(image_data@coordinates))
common_cells <- intersect(Cells(x = fry_seurat), rownames(image.data@coordinates))
Error in h(simpleError(msg, call)) : 
  error in evaluating the argument 'y' in selecting a method for function 'intersect': error in evaluating the argument 'x' in selecting a method for function 'rownames': object 'image.data' not found
# load spaceranger outputs directly into Seurat object 
spaceranger_seurat <- Load10X_Spatial(spaceranger_out)
'giveCsparse' has been deprecated; setting 'repr = "T"' for you

Comparing Seurat and Spatial Experiment

Now let’s take a look at how each of these objects stores information for the experiment. We will just look at the files that have been produced from Spaceranger only and compare the Seurat object to the SpatialExperiment.

head(colData(spaceranger_spe))
DataFrame with 6 rows and 1 column
                     sample_id
                   <character>
AAACAACGAATAGTTC-1 SCPCR000372
AAACAAGTATCTCCCA-1 SCPCR000372
AAACAATCTACTAGCA-1 SCPCR000372
AAACACCAATAACTGC-1 SCPCR000372
AAACAGAGCGACTCCT-1 SCPCR000372
AAACAGCTTTCAGAAG-1 SCPCR000372
head(spaceranger_seurat@meta.data)

Seurat automatically adds some per cell QC metrics here, but in general the colData looks similar to what we would expect for a SingleCellExperiment for the SpatialExperiment.

head(rowData(spaceranger_spe))
DataFrame with 6 rows and 1 column
                     symbol
                <character>
ENSG00000186092       OR4F5
ENSG00000284733      OR4F29
ENSG00000284662      OR4F16
ENSG00000187634      SAMD11
ENSG00000188976       NOC2L
ENSG00000187961      KLHL17
head(spaceranger_seurat[["Spatial"]]@meta.features)
spaceranger_spe <- spaceranger_spe %>%
  scuttle::addPerCellQCMetrics(subsets = list(mito_genes = mito_genes[mito_genes %in% rownames(spaceranger_spe)])) 
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
head(colData(spaceranger_spe))
DataFrame with 6 rows and 7 columns
                     sample_id       sum  detected subsets_mito_genes_sum subsets_mito_genes_detected
                   <character> <numeric> <integer>              <numeric>                   <integer>
AAACAACGAATAGTTC-1 SCPCR000372      9094      3963                    148                          10
AAACAAGTATCTCCCA-1 SCPCR000372      7070      2993                    304                          10
AAACAATCTACTAGCA-1 SCPCR000372     14917      5212                    313                          11
AAACACCAATAACTGC-1 SCPCR000372      3999      2280                     93                          10
AAACAGAGCGACTCCT-1 SCPCR000372     14765      5050                    139                          10
AAACAGCTTTCAGAAG-1 SCPCR000372      3972      2174                    134                           9
                   subsets_mito_genes_percent     total
                                    <numeric> <numeric>
AAACAACGAATAGTTC-1                   1.627447      9094
AAACAAGTATCTCCCA-1                   4.299859      7070
AAACAATCTACTAGCA-1                   2.098277     14917
AAACACCAATAACTGC-1                   2.325581      3999
AAACAGAGCGACTCCT-1                   0.941416     14765
AAACAGCTTTCAGAAG-1                   3.373615      3972

You can still use the same functions to add in per cell metrics that we are using in scpcaTools now on a SpatialExperiment.

Let’s specifically look at how each of them stores the data related to the spatial information. The SpatialExperiment has three places where it stores spatial information, spatialData, spatialCoords, and imgData.

spatialData(spaceranger_spe)
DataFrame with 4992 rows and 3 columns
                   in_tissue array_row array_col
                   <logical> <integer> <integer>
AAACAACGAATAGTTC-1     FALSE         0        16
AAACAAGTATCTCCCA-1      TRUE        50       102
AAACAATCTACTAGCA-1     FALSE         3        43
AAACACCAATAACTGC-1     FALSE        59        19
AAACAGAGCGACTCCT-1      TRUE        14        94
...                      ...       ...       ...
TTGTTTCACATCCAGG-1      TRUE        58        42
TTGTTTCATTAGTCTA-1      TRUE        60        30
TTGTTTCCATACAACT-1      TRUE        45        27
TTGTTTGTATTACACG-1      TRUE        73        41
TTGTTTGTGTAAATTC-1      TRUE         7        51
head(spatialCoords(spaceranger_spe))
                   pxl_col_in_fullres pxl_row_in_fullres
AAACAACGAATAGTTC-1               1370               2280
AAACAAGTATCTCCCA-1               6047               6970
AAACAATCTACTAGCA-1               1642               3744
AAACACCAATAACTGC-1               6927               2483
AAACAGAGCGACTCCT-1               2659               6513
AAACAGCTTTCAGAAG-1               5424               1930

spatialData holds information about whether or not each barcode was covered by tissue and spatial metadata. spatialCoords holds the coordinates for each spot.

This same information can be found by looking in the images slot in the Seurat object.

imgData holds information about each image that is stored as multiple slices of an image can be stored within one SpatialExperiment.

imgData(spaceranger_spe)
DataFrame with 1 row and 4 columns
    sample_id    image_id   data scaleFactor
  <character> <character> <list>   <numeric>
1 SCPCR000372      lowres   ####        0.06

This same information can be found by looking at the images slot in the Seurat object.

spaceranger_seurat@images$slice1@scale.factors
$spot
[1] 0.2

$fiducial
[1] 113.7005

$hires
[1] 0.2

$lowres
[1] 0.06

attr(,"class")
[1] "scalefactors"

Let’s take a look at some example plots that can be produced with each of these objects.

# plot tissue spots coloring by total UMI count
plotSpots(spe = spaceranger_spe, 
          x_coord = "pxl_col_in_fullres", 
          y_coord = "pxl_row_in_fullres", 
          annotate = "sum") +
  scale_color_viridis_c()
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing
scale.

SpatialFeaturePlot(spaceranger_seurat, features = "nCount_Spatial")
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

It looks like overall they both would work and that the SpatialExperiment will provide similar functionality to how we have things already set up for single-cell experiments. I think I have a slight preference for the Seurat plots as you can adjust the transparency of the points and also see the tissue, but I also think we may be able to play around with the plots with the SpatialExperiment more if we want something similar to that.

Alevin-fry + Spaceranger versus Spaceranger Only

Now let’s take a look at comparing the two methods of using Alevin-fry + Spaceranger to only Spaceranger for quantification. To do this, we will read in a list of Alevin-fry + Spaceranger SpatialExperiment objects and Spaceranger objects and then merge them into one list before grabbing the per cell and per gene quality metrics.

Create Spatial Experiments

# create sample info dataframe to be joined with per cell dataframe later
sample_info_df <- quant_info_table(data_dir= s3_out, 
                 tools = c("cellranger", "alevin-fry-knee"),
                 samples = sample_ids) %>%
  # convert cellranger to spaceranger 
  dplyr::mutate(tool = ifelse(tool == "cellranger", "spaceranger", tool))
# modify sample data frame to get spatial and fry directories for each sample
input_df <- sample_info_df %>%
  dplyr::select(tool, quant_dir, sample, data_dir) %>%
  dplyr::mutate(data_dir = dplyr::case_when(tool == "spaceranger" ~ 
                                            file.path(data_dir, "outs", "spatial"),
                                            tool == "alevin-fry" ~ data_dir)) %>%
  tidyr::pivot_wider(id_cols = sample, names_from = tool, values_from = data_dir)

# use custome function to create list of spe's
fry_spe_list <- mapply(convert_fry_spaceranger_spe,
                       input_df$`alevin-fry`,
                       input_df$spaceranger,
                       input_df$sample)
Rows: 4992 Columns: 6
── Column specification ──────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): barcode
dbl (5): in_tissue, array_row, array_col, pxl_row_in_fullres, pxl_col_in_fullres

β„Ή Use `spec()` to retrieve the full column specification for this data.
β„Ή Specify the column types or set `show_col_types = FALSE` to quiet this message.
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Rows: 4992 Columns: 6
── Column specification ──────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): barcode
dbl (5): in_tissue, array_row, array_col, pxl_row_in_fullres, pxl_col_in_fullres

β„Ή Use `spec()` to retrieve the full column specification for this data.
β„Ή Specify the column types or set `show_col_types = FALSE` to quiet this message.
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
names(fry_spe_list) <- paste0(sample_ids, "-fry-spaceranger-combined")
# create list of spaceranger only processed spe's 
# spaceranger output paths
spaceranger_outs <- file.path(s3_out, "cellranger", cellranger_folders, "outs")
names(spaceranger_outs) <- sample_ids

spaceranger_spe_list <- purrr::imap(spaceranger_outs, spaceranger_only_spe)
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
names(spaceranger_spe_list) <- paste0(sample_ids, "-spaceranger-only")
all_spe_list <- append(fry_spe_list, spaceranger_spe_list)

# calculate per cell QC and output to a combined data frame with plotting 
all_spe_list <- all_spe_list %>%
    purrr::map(
      ~ scuttle::addPerCellQCMetrics(.x, 
                                     subsets = list(mito = mito_genes[mito_genes %in% rownames(.x)]))) %>%
  purrr::map(scuttle::addPerFeatureQCMetrics)
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’

Per Spot QC Metrics

# create custom function to grab colData and convert to data frame 
spatial_coldata_to_df <- function(spe){
  df <- as.data.frame(colData(spe)) %>%
    tibble::rownames_to_column(var = "spot_id")
}
fry_samples = c("SCPCR000372-fry-spaceranger-combined", "SCPCR000373-fry-spaceranger-combined")
spaceranger_samples = c("SCPCR000372-spaceranger-only", "SCPCR000373-spaceranger-only")

# join coldata dataframe with sample info
coldata_df <- purrr::map_df(all_spe_list,spatial_coldata_to_df, .id = "sample") %>%
  dplyr::mutate(tool = dplyr::case_when(sample %in% fry_samples ~ "alevin-fry",
                                        sample %in% spaceranger_samples ~ "spaceranger"),
                # extract sample name only to allow for joining
                sample = stringr::word(sample, 1, sep = "-"),
                # remove extra -1 from spaceranger barcodes
                spot_id = gsub("-1", "", spot_id))%>%
  dplyr::left_join(sample_info_df,
                   by = c("sample", "tool"))
# identify shared spots only 
spot_counts <- coldata_df %>%  
  dplyr::count(spot_id, sample)

common_spots <- spot_counts %>%
  dplyr::filter(n == 2) %>%
  dplyr::pull(spot_id)

coldata_df_common <- coldata_df %>%
  dplyr::filter(spot_id %in% common_spots)

In addition to filtering on shared spots we will also want to filter based on spots that are found to overlap with tissue.

# also want to filter the spe's directly 
filter_spe <- function(spe){
  spe <- spe[, spatialData(spe)$in_tissue == 1]
}

all_spes_filter <- all_spe_list %>%
  purrr::map(filter_spe)
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
# custom function for plotting spe results 
plot_spe <- function(spe, sample, column){
  #column_sym <- rlang::sym(column)
  plotSpots(spe, 
            x_coord = "pxl_col_in_fullres", 
            y_coord = "pxl_row_in_fullres", 
            annotate = column) +
  scale_color_viridis_c() + 
    ggtitle(sample)
}

First we will look at the per cell metrics: mitochondrial reads per cell, total UMI per cell, and total genes detected per cell.

# % mitochondrial reads/ spot 
ggplot(coldata_df_common, aes(x = tool, y = subsets_mito_percent, fill = tool)) + 
  geom_boxplot() + 
  facet_grid(~ sample) +
  theme_classic() + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  ylab("Mito Percent") + 
  xlab("")

all_spes_filter %>%
  purrr::imap(plot_spe, "subsets_mito_percent")
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing
scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing
scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing
scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing
scale.
$`SCPCR000372-fry-spaceranger-combined`

$`SCPCR000373-fry-spaceranger-combined`

$`SCPCR000372-spaceranger-only`

$`SCPCR000373-spaceranger-only`

Overall it looks like mitochondrial content is low and fairly similar across both tools.

all_spes_filter %>%
  purrr::imap(plot_spe, "sum")
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing
scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing
scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing
scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing
scale.
$`SCPCR000372-fry-spaceranger-combined`

$`SCPCR000373-fry-spaceranger-combined`

$`SCPCR000372-spaceranger-only`

$`SCPCR000373-spaceranger-only`

Interestingly when looking at the pictures (besides that the Alevin-fry and Spaceranger pictures are flipped), there appears to be different patterns of total UMI count per spot showing up between fry and Spaceranger.

all_spes_filter %>%
  purrr::imap(plot_spe, "detected")
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing
scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing
scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing
scale.
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing
scale.
$`SCPCR000372-fry-spaceranger-combined`

$`SCPCR000373-fry-spaceranger-combined`

$`SCPCR000372-spaceranger-only`

$`SCPCR000373-spaceranger-only`

Generally it looks like both tools are fairly similar in the number of UMI/cell and genes detected/cell but the actual spatial distribution of looks very different within each sample across both tools which is concerning.

Per Gene QC Metrics

Let’s also look at the correlation of mean gene expression across shared genes.

spatial_rowdata_to_df <- function(spe){
  df <- as.data.frame(rowData(spe)) %>%
    tibble::rownames_to_column(var = "gene_id")
}
# combine rowdata with sample info
rowdata_df <- purrr::map_df(all_spe_list,spatial_rowdata_to_df, .id = "sample") %>%
  dplyr::mutate(tool = dplyr::case_when(sample %in% fry_samples ~ "alevin-fry",
                                        sample %in% spaceranger_samples ~ "spaceranger"),
                sample = stringr::word(sample, 1, sep = "-")) %>%
  dplyr::left_join(sample_info_df,
                   by = c("sample", "tool"))


gene_counts <- rowdata_df %>% 
  # remove genes that have a low frequency of being detected
  dplyr::filter(detected >= 5.0) %>%
  dplyr::count(gene_id, sample)

# restrict to only common genes 
common_genes <- gene_counts %>%
  dplyr::filter(n == 2) %>%
  dplyr::pull(gene_id)

rowdata_df_common <- rowdata_df %>%
  dplyr::filter(
    (gene_id %in% common_genes) 
  )
rowdata_cor <- rowdata_df_common %>%
  dplyr::select(tool, gene_id, sample, mean) %>%
  # spread the mean expression stats to one column per caller
  tidyr::pivot_wider(id_cols = c(gene_id, sample),
                     names_from = c("tool"),
                     values_from = mean) %>%
  # drop rows with NA values to ease correlation calculation
  tidyr::drop_na()
# look at correlation between the two tools
rowdata_cor %>% 
  dplyr::group_by(sample) %>%
  dplyr::summarize(
    alevin_fry_knee_spaceranger_cor = cor(`spaceranger`, `alevin-fry`, method = "spearman")
  )
# mean gene expression across shared genes 
ggplot(rowdata_cor, aes(x = `spaceranger`, y = `alevin-fry`)) +
  geom_point(size = 0.5, alpha = 0.1) + 
  facet_wrap(~ sample) + 
  scale_x_log10() + 
  scale_y_log10() + 
  labs(x = "Spaceranger mean gene expression", y = "Alevin Fry Mean gene expression") + 
  theme_classic()

Shared genes across tools

The final thing we will look at is the overlap of genes detected in each tool. First we need to filter the SpatialExperiment objects to only have spots that are found in both tools and then we filter to include only genes that are found in both indices before looking at the overlap.

# make a function to filter sces by subset of spots and re-calculate feature stats
filter_spe <- function(spe, spots){
  # remove "-1" at end of barcode for spaceranger spes
  colnames(spe) <- gsub("-1", "", colnames(spe))
  cells_to_keep <- colnames(spe) %in% spots
  rowData(spe) <- NULL
  spe[, cells_to_keep] %>%
    scuttle::addPerFeatureQCMetrics()
}
# filter all spes to only have common spots 
spe_list_common <- all_spes_filter %>%
  purrr::map(filter_spe, spots = common_spots)
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
Found more than one class "SpatialImage" in cache; using the first, from namespace 'SeuratObject'
Also defined by β€˜SpatialExperiment’
# grab rowdata from filtered sces 
rowdata_df_filtered <- purrr::map_df(spe_list_common, spatial_rowdata_to_df, .id = "sample") %>%
  dplyr::mutate(tool = dplyr::case_when(sample %in% fry_samples ~ "alevin-fry",
                                        sample %in% spaceranger_samples ~ "spaceranger"),
                sample = stringr::word(sample, 1, sep = "-")) %>%
  dplyr::left_join(sample_info_df,
                   by = c("sample", "tool"))
# get genes common in all tools
common_genes <- rowdata_df_filtered %>%
  dplyr::select(gene_id, tool) %>%
  dplyr::distinct() %>%
  dplyr::group_by(gene_id) %>%
  dplyr::tally() %>%
  # filter for genes found in both spaceranger and alevin-fry
  dplyr::filter(n == 2) %>%
  dplyr::pull(gene_id)

# filter rowdata_df to only include genes found in all tools and genes with mean > 0 and detected > 0 in all cells
rowdata_df_filtered <- rowdata_df_filtered %>% 
  dplyr::filter(gene_id %in% common_genes) %>%
  dplyr::filter(mean > 0 & detected > 0)
# create upset plot 
gene_detect_df <- rowdata_df_filtered %>%
  dplyr::group_by(gene_id) %>%
  dplyr::summarise(tools_detected = list(unique(tool)))

ggplot(gene_detect_df, aes(x = tools_detected)) +
  geom_bar() +
  scale_x_upset(n_intersections = 3)

Similar to with the single-cell data most genes are detected in both Alevin-fry and Spaceranger which is good.

Concluding thoughts

  • It appears that we can use the SpatialExperiment to store the quantification output and it will maintain similar functionality to the SingleCellExperiment obejcts we use for single-cell and single-nuclei data.
  • Although Alevin-fry and Spaceranger only result in similar distributions of UMI/spot and genes/spot, the patterning across the tissue is not the same which is not ideal and doesn’t provide support for adding the use of Alevin-fry to our workflow (since we have to use Spaceranger anyways).

Session Info

sessioninfo::session_info()
─ Session info  πŸ₯¦  πŸ§”πŸΏ  πŸ˜‹   ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 hash: broccoli, person: dark skin tone, beard, face savoring food

 setting  value
 version  R version 4.1.1 (2021-08-10)
 os       macOS Catalina 10.15.7
 system   x86_64, darwin17.0
 ui       RStudio
 language (EN)
 collate  en_US.UTF-8
 ctype    en_US.UTF-8
 tz       America/Chicago
 date     2021-11-17
 rstudio  1.4.1106 Tiger Daylily (desktop)
 pandoc   2.11.4 @ /Applications/RStudio.app/Contents/MacOS/pandoc/ (via rmarkdown)

─ Packages ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 package              * version  date (UTC) lib source
 assertthat             0.2.1    2019-03-21 [1] CRAN (R 4.1.0)
 beachmat               2.10.0   2021-10-26 [1] Bioconductor
 Biobase              * 2.54.0   2021-10-26 [1] Bioconductor
 BiocGenerics         * 0.40.0   2021-10-26 [1] Bioconductor
 BiocParallel           1.28.0   2021-10-26 [1] Bioconductor
 bit                    4.0.4    2020-08-04 [1] CRAN (R 4.1.0)
 bit64                  4.0.5    2020-08-30 [1] CRAN (R 4.1.0)
 bitops                 1.0-7    2021-04-24 [1] CRAN (R 4.1.0)
 bslib                  0.3.1    2021-10-06 [1] CRAN (R 4.1.0)
 cli                    3.1.0    2021-10-27 [1] CRAN (R 4.1.1)
 colorspace             2.0-2    2021-06-24 [1] CRAN (R 4.1.0)
 crayon                 1.4.2    2021-10-29 [1] CRAN (R 4.1.0)
 data.table             1.14.2   2021-09-27 [1] CRAN (R 4.1.0)
 DBI                    1.1.1    2021-01-15 [1] CRAN (R 4.1.0)
 DelayedArray           0.20.0   2021-10-26 [1] Bioconductor
 DelayedMatrixStats     1.16.0   2021-10-26 [1] Bioconductor
 digest                 0.6.28   2021-09-23 [1] CRAN (R 4.1.0)
 dplyr                  1.0.7    2021-06-18 [1] CRAN (R 4.1.0)
 dqrng                  0.3.0    2021-05-01 [1] CRAN (R 4.1.0)
 DropletUtils           1.14.1   2021-11-08 [1] Bioconductor
 edgeR                  3.36.0   2021-10-26 [1] Bioconductor
 ellipsis               0.3.2    2021-04-29 [1] CRAN (R 4.1.0)
 evaluate               0.14     2019-05-28 [1] CRAN (R 4.1.0)
 fansi                  0.5.0    2021-05-25 [1] CRAN (R 4.1.0)
 fastmap                1.1.0    2021-01-25 [1] CRAN (R 4.1.0)
 generics               0.1.1    2021-10-25 [1] CRAN (R 4.1.0)
 GenomeInfoDb         * 1.30.0   2021-10-26 [1] Bioconductor
 GenomeInfoDbData       1.2.7    2021-11-16 [1] Bioconductor
 GenomicRanges        * 1.46.0   2021-10-26 [1] Bioconductor
 ggplot2              * 3.3.5    2021-06-25 [1] CRAN (R 4.1.0)
 ggside                 0.1.3    2021-10-24 [1] CRAN (R 4.1.0)
 ggspavis             * 1.0.0    2021-10-26 [1] Bioconductor
 ggupset              * 0.3.0    2020-05-05 [1] CRAN (R 4.1.0)
 glue                   1.5.0    2021-11-07 [1] CRAN (R 4.1.0)
 gtable                 0.3.0    2019-03-25 [1] CRAN (R 4.1.0)
 HDF5Array              1.22.1   2021-11-14 [1] Bioconductor
 here                   1.0.1    2020-12-13 [1] CRAN (R 4.1.0)
 hms                    1.1.1    2021-09-26 [1] CRAN (R 4.1.0)
 htmltools              0.5.2    2021-08-25 [1] CRAN (R 4.1.0)
 htmlwidgets            1.5.4    2021-09-08 [1] CRAN (R 4.1.0)
 IRanges              * 2.28.0   2021-10-26 [1] Bioconductor
 jquerylib              0.1.4    2021-04-26 [1] CRAN (R 4.1.0)
 jsonlite               1.7.2    2020-12-09 [1] CRAN (R 4.1.0)
 knitr                  1.36     2021-09-29 [1] CRAN (R 4.1.0)
 lattice                0.20-45  2021-09-22 [1] CRAN (R 4.1.0)
 lifecycle              1.0.1    2021-09-24 [1] CRAN (R 4.1.0)
 limma                  3.50.0   2021-10-26 [1] Bioconductor
 locfit                 1.5-9.4  2020-03-25 [1] CRAN (R 4.1.0)
 magick                 2.7.3    2021-08-18 [1] CRAN (R 4.1.0)
 magrittr             * 2.0.1    2020-11-17 [1] CRAN (R 4.1.0)
 Matrix                 1.3-4    2021-06-01 [1] CRAN (R 4.1.1)
 MatrixGenerics       * 1.6.0    2021-10-26 [1] Bioconductor
 matrixStats          * 0.61.0   2021-09-17 [1] CRAN (R 4.1.0)
 munsell                0.5.0    2018-06-12 [1] CRAN (R 4.1.0)
 pillar                 1.6.4    2021-10-18 [1] CRAN (R 4.1.1)
 pkgconfig              2.0.3    2019-09-22 [1] CRAN (R 4.1.0)
 purrr                  0.3.4    2020-04-17 [1] CRAN (R 4.1.0)
 R.methodsS3            1.8.1    2020-08-26 [1] CRAN (R 4.1.0)
 R.oo                   1.24.0   2020-08-26 [1] CRAN (R 4.1.0)
 R.utils                2.11.0   2021-09-26 [1] CRAN (R 4.1.0)
 R6                     2.5.1    2021-08-19 [1] CRAN (R 4.1.0)
 Rcpp                   1.0.7    2021-07-07 [1] CRAN (R 4.1.0)
 RCurl                  1.98-1.5 2021-09-17 [1] CRAN (R 4.1.0)
 readr                  2.1.0    2021-11-11 [1] CRAN (R 4.1.0)
 rhdf5                  2.38.0   2021-10-26 [1] Bioconductor
 rhdf5filters           1.6.0    2021-10-26 [1] Bioconductor
 Rhdf5lib               1.16.0   2021-10-26 [1] Bioconductor
 rjson                  0.2.20   2018-06-08 [1] CRAN (R 4.1.0)
 rlang                  0.4.12   2021-10-18 [1] CRAN (R 4.1.1)
 rmarkdown              2.11     2021-09-14 [1] CRAN (R 4.1.0)
 rprojroot              2.0.2    2020-11-15 [1] CRAN (R 4.1.0)
 rstudioapi             0.13     2020-11-12 [1] CRAN (R 4.1.0)
 S4Vectors            * 0.32.2   2021-11-07 [1] Bioconductor
 sass                   0.4.0    2021-05-12 [1] CRAN (R 4.1.0)
 scales                 1.1.1    2020-05-11 [1] CRAN (R 4.1.0)
 scpcaTools             0.1.2    2021-10-12 [1] Github (AlexsLemonade/scpcaTools@2cdad4c)
 scuttle                1.4.0    2021-10-26 [1] Bioconductor
 sessioninfo            1.2.1    2021-11-02 [1] CRAN (R 4.1.0)
 SeuratObject         * 4.0.3    2021-11-10 [1] CRAN (R 4.1.0)
 SingleCellExperiment * 1.16.0   2021-10-26 [1] Bioconductor
 sparseMatrixStats      1.6.0    2021-10-26 [1] Bioconductor
 SpatialExperiment    * 1.4.0    2021-10-26 [1] Bioconductor
 stringi                1.7.5    2021-10-04 [1] CRAN (R 4.1.1)
 stringr                1.4.0    2019-02-10 [1] CRAN (R 4.1.0)
 SummarizedExperiment * 1.24.0   2021-10-26 [1] Bioconductor
 tibble                 3.1.6    2021-11-07 [1] CRAN (R 4.1.0)
 tidyr                  1.1.4    2021-09-27 [1] CRAN (R 4.1.0)
 tidyselect             1.1.1    2021-04-30 [1] CRAN (R 4.1.0)
 tzdb                   0.2.0    2021-10-27 [1] CRAN (R 4.1.1)
 utf8                   1.2.2    2021-07-24 [1] CRAN (R 4.1.0)
 vctrs                  0.3.8    2021-04-29 [1] CRAN (R 4.1.0)
 vroom                  1.5.6    2021-11-10 [1] CRAN (R 4.1.0)
 withr                  2.4.2    2021-04-18 [1] CRAN (R 4.1.0)
 xfun                   0.28     2021-11-04 [1] CRAN (R 4.1.0)
 XVector                0.34.0   2021-10-26 [1] Bioconductor
 yaml                   2.2.1    2020-02-01 [1] CRAN (R 4.1.0)
 zlibbioc               1.40.0   2021-10-26 [1] Bioconductor

 [1] /Library/Frameworks/R.framework/Versions/4.1/Resources/library

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
LS0tCnRpdGxlOiAiU3BhdGlhbCBUcmFuc2NyaXB0b21pY3MgRXhwbG9yYXRpb24iCmF1dGhvcjogIkFsbHkgSGF3a2lucyBmb3IgQ0NETCIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKLS0tCgpJbiB0aGlzIG5vdGVib29rIHdlIGFyZSBjb21wYXJpbmcgdGhlIHVzZSBvZiBBbGV2aW4tZnJ5IGFuZCBTcGFjZXJhbmdlciBmb3IgcXVhbnRpZnlpbmcgc3BhdGlhbCB0cmFuc2NyaXB0b21pY3MgbGlicmFyaWVzLiAKVHdvIHNwYXRpYWwgdHJhbnNjcmlwdG9taWMgbGlicmFyaWVzIHdlcmUgcXVhbnRpZmllZCB1c2luZyBBbGV2aW4tZnJ5IGFuZCBTcGFjZXJhbmdlciBhbmQgdGhlIHJlc3VsdHMgd2VyZSBjb21iaW5lZCBmb2xsb3dpbmcgdGhlIFtBbGV2aW4tZnJ5IHR1dG9yaWFsXShodHRwczovL2NvbWJpbmUtbGFiLmdpdGh1Yi5pby9hbGV2aW4tZnJ5LXR1dG9yaWFscy8yMDIxL2FmLXNwYXRpYWwvKS4gCldlIGFyZSBjb21wYXJpbmcgdGhhdCB0byBpZiB3ZSB3ZXJlIHRvIG5vdCBpbnRlZ3JhdGUgdGhlIFNwYWNlcmFuZ2VyIGRhdGEgd2l0aCBBbGV2aW4tZnJ5IGFuZCBvbmx5IHVzZSBTcGFjZXJhbmdlci4gCgpBZGRpdGlvbmFsbHkgaW4gdGhpcyBub3RlYm9vayB3ZSBhbHNvIGV4cGxvcmUgdGhlIHVzZSBvZiBzdG9yaW5nIHNwYXRpYWwgZGF0YSBpbiBhIFtgU3BhdGlhbEV4cGVyaW1lbnRgIG9iamVjdF0oaHR0cHM6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL3JlbGVhc2UvYmlvYy92aWduZXR0ZXMvU3BhdGlhbEV4cGVyaW1lbnQvaW5zdC9kb2MvU3BhdGlhbEV4cGVyaW1lbnQuaHRtbCMzX0NvbW1vbl9vcGVyYXRpb25zKSBvciBpbiBhIFtgU2V1cmF0YCBvYmplY3RdKGh0dHBzOi8vc2F0aWphbGFiLm9yZy9zZXVyYXQvYXJ0aWNsZXMvc3BhdGlhbF92aWduZXR0ZS5odG1sKS4gCgojIyBTZXQgVXAKCmBgYHtyfQpsaWJyYXJ5KG1hZ3JpdHRyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCmxpYnJhcnkoU3BhdGlhbEV4cGVyaW1lbnQpCmxpYnJhcnkoZ2dzcGF2aXMpCmxpYnJhcnkoZ2d1cHNldCkKYGBgCgpgYGB7cn0KIyBsb2FkIGluIGJlbmNobWFya2luZyBmdW5jdGlvbnMgdGhhdCB3aWxsIGJlIHVzZWQgZm9yIGNvcHlpbmcgZGF0YSBhbmQgZ2VuZXJhdGluZyBzYW1wbGUgdGFibGVzCmZ1bmN0aW9uX3BhdGggPC0gZmlsZS5wYXRoKCIuLiIgLCJiZW5jaG1hcmtpbmctZnVuY3Rpb25zIiwgIlIiKQpmaWxlLnBhdGgoZnVuY3Rpb25fcGF0aCwgbGlzdC5maWxlcyhmdW5jdGlvbl9wYXRoLCBwYXR0ZXJuID0gIiouUiQiKSkgJT4lCiAgcHVycnI6OndhbGsoc291cmNlKQpgYGAKCgpgYGB7cn0KIyBzZXQgdXAgZmlsZSBwYXRocyAKYmFzZV9kaXIgPC0gaGVyZTo6aGVyZSgpCgojIG91dHB1dCBmb2xkZXIgdG8gc3RvcmUgYWxldmluLWZyeSBhbmQgY2VsbHJhbmdlciBxdWFudHMgZnJvbSBTMwpkYXRhX2RpciA8LSBmaWxlLnBhdGgoYmFzZV9kaXIsICJkYXRhIiwgInNwYXRpYWwiKSAKczNfb3V0IDwtIGZpbGUucGF0aChkYXRhX2RpciwgImRhdGEiLCAicXVhbnRzIikKCiMgb3V0cHV0IGZvbGRlciBmb3IgaW1hZ2UgZmlsZXMgCmltYWdlX2RpciA8LSBmaWxlLnBhdGgoZGF0YV9kaXIsICJkYXRhIiwgImltYWdlcyIpCgojIHJlc3VsdHMgZGlyZWN0b3J5IApyZXN1bHRzX2RpciA8LSBmaWxlLnBhdGgoZGF0YV9kaXIsICJyZXN1bHRzIikKCiMgY3JlYXRlIGRpcmVjdG9yaWVzCmlmKCFkaXIuZXhpc3RzKGRhdGFfZGlyKSl7CiAgZGlyLmNyZWF0ZShkYXRhX2RpciwgcmVjdXJzaXZlID0gVFJVRSkKfQoKaWYoIWRpci5leGlzdHMoaW1hZ2VfZGlyKSl7CiAgZGlyLmNyZWF0ZShpbWFnZV9kaXIsIHJlY3Vyc2l2ZSA9IFRSVUUpCn0KCmlmKCFkaXIuZXhpc3RzKHJlc3VsdHNfZGlyKSl7CiAgZGlyLmNyZWF0ZShyZXN1bHRzX2RpciwgcmVjdXJzaXZlID0gVFJVRSkKfQpgYGAKCgpgYGB7cn0KbWl0b19maWxlIDwtIGZpbGUucGF0aChiYXNlX2RpciwgInNhbXBsZS1pbmZvIiwgIkhvbW9fc2FwaWVucy5HUkNoMzguMTAzLm1pdG9nZW5lcy50eHQiKQogIAojIHJlYWQgaW4gbWl0byBnZW5lcyAKbWl0b19nZW5lcyA8LSByZWFkcjo6cmVhZF90c3YobWl0b19maWxlLCBjb2xfbmFtZXMgPSAiZ2VuZV9pZCIpCm1pdG9fZ2VuZXMgPC0gbWl0b19nZW5lcyAlPiUKICBkcGx5cjo6cHVsbChnZW5lX2lkKSAlPiUKICB1bmlxdWUoKQpgYGAKCgojIyBDb3B5IGRhdGEgZnJvbSBTMwoKYGBge3J9CiMgZ3JhYiBhbGV2aW4gZnJ5IGFuZCBjZWxscmFuZ2VyIG91dHB1dApzYW1wbGVfaWRzIDwtIGMoIlNDUENSMDAwMzcyIiwgIlNDUENSMDAwMzczIikKCmF3c19jb3B5X3NhbXBsZXMobG9jYWxfZGlyID0gczNfb3V0LAogICAgICAgICAgICAgICAgIHMzX2RpciA9ICJzMzovL25leHRmbG93LWNjZGwtcmVzdWx0cy9zY3BjYSIsCiAgICAgICAgICAgICAgICAgc2FtcGxlcyA9IHNhbXBsZV9pZHMsCiAgICAgICAgICAgICAgICAgdG9vbHMgPSBjKCJhbGV2aW4tZnJ5LWtuZWUiLCAiY2VsbHJhbmdlciIpKQpgYGAKCiMjIENvbXBhcmluZyBTcGF0aWFsRXhwZXJpbWVudCB0byBTZXVyYXQKCiMjIyBJbXBvcnRpbmcgQWxldmluLWZyeSBhbmQgU3BhY2VyYW5nZXIgb3V0cHV0IGludG8gYSBTcGF0aWFsRXhwZXJpbWVudAoKRmlyc3Qgd2UgYXJlIHRlc3RpbmcgdXNlIG9mIGEgYFNwYXRpYWxFeHBlcmltZW50YCBvYmplY3QgdG8gc3RvcmUgdGhlIHNwYXRpYWwgdHJhbnNjcmlwdG9taWNzIHJlc3VsdHMuIApXZSB3aWxsIGNyZWF0ZSB0d28gZnVuY3Rpb25zLCBvbmUgdG8gZGVhbCB3aXRoIGNyZWF0aW5nIHRoZSBjb21iaW5lZCBvYmplY3QgZnJvbSBib3RoIEFsZXZpbi1mcnkgYW5kIFNwYWNlcmFuZ2VyIG91dHB1dCwgYW5kIHRoZSBzZWNvbmQgdG8gaW1wb3J0IFNwYWNlcmFuZ2VyIG91dHB1dCBvbmx5LiAKCmBgYHtyfQojIGN1c3RvbSBmdW5jdGlvbiBmb3IgY29udmVydGluZyBBbGV2aW4tZnJ5IGFuZCBTcGFjZXJhbmdlciBvdXRwdXQgdG8gc3BlCmNvbnZlcnRfZnJ5X3NwYWNlcmFuZ2VyX3NwZSA8LSBmdW5jdGlvbihmcnlfZGlyLCBzcGFjZXJhbmdlcl9kaXIsIHNhbXBsZV9uYW1lKXsKICBmcnlfc2NlIDwtIHNjcGNhVG9vbHM6OnJlYWRfYWxldmluKGZyeV9kaXIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXNhX21vZGUgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2hpY2hfY291bnRzID0gInNwbGljZWQiKQogIAogICMgcmVhZCBpbiBpbWFnZSBkYXRhCiAgaW1hZ2VfZGF0YSA8LSBTcGF0aWFsRXhwZXJpbWVudDo6cmVhZEltZ0RhdGEoc3BhY2VyYW5nZXJfZGlyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX2lkID0gc2FtcGxlX25hbWUpCiAgCiAgIyByZWFkIGluIHNwYXRpYWwgY29vcmRpbmF0ZXMgCiAgc3BhdGlhbF9kYXRhX2ZpbGUgPC0gZmlsZS5wYXRoKHNwYWNlcmFuZ2VyX2RpciwgInRpc3N1ZV9wb3NpdGlvbnNfbGlzdC5jc3YiKQogIHNwYXRpYWxfZGF0YSA8LSByZWFkcjo6cmVhZF9jc3Yoc3BhdGlhbF9kYXRhX2ZpbGUsIGNvbF9uYW1lcyA9IGMoImJhcmNvZGUiLCAiaW5fdGlzc3VlIiwgImFycmF5X3JvdyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYXJyYXlfY29sIiwicHhsX3Jvd19pbl9mdWxscmVzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJweGxfY29sX2luX2Z1bGxyZXMiKSkKICAjIGZpbmQgY29tbW9uIGNlbGxzIGJldHdlZW4gQWxldmluLWZyeSBhbmQgc3BhY2VyYW5nZXIgCiAgc3BhdGlhbF9kYXRhIDwtIHNwYXRpYWxfZGF0YSAlPiUKICAgIGRwbHlyOjptdXRhdGUoYmFyY29kZSA9IGdzdWIoIi0xIiwgIiIsIGJhcmNvZGUpKQogIGNvbW1vbl9jZWxscyA8LSBpbnRlcnNlY3QoY29sbmFtZXMoZnJ5X3NjZSksIHNwYXRpYWxfZGF0YSRiYXJjb2RlKQogIAogICMgc3Vic2V0IGZyeV9zY2UgYnkgY29tbW9uIGNlbGxzCiAgZnJ5X3NjZV9zdWJzZXQgPC0gZnJ5X3NjZVssY29tbW9uX2NlbGxzXQogIHNwYXRpYWxfZGF0YV9zdWJzZXQgPC0gc3BhdGlhbF9kYXRhICU+JQogICAgZHBseXI6OmZpbHRlcihiYXJjb2RlICVpbiUgY29tbW9uX2NlbGxzKQogIAogICMgY29uc3RydWN0ICdTcGF0aWFsRXhwZXJpbWVudCcKICBmcnlfc3BlIDwtIFNwYXRpYWxFeHBlcmltZW50KAogICAgYXNzYXlzID0gbGlzdChjb3VudHMgPSBhc3NheShmcnlfc2NlX3N1YnNldCkpLAogICAgY29sRGF0YSA9IGNvbERhdGEoZnJ5X3NjZV9zdWJzZXQpLCAKICAgIGltZ0RhdGEgPSBpbWFnZV9kYXRhLAogICAgc3BhdGlhbERhdGEgPSBEYXRhRnJhbWUoc3BhdGlhbF9kYXRhX3N1YnNldCksCiAgICBzcGF0aWFsQ29vcmRzTmFtZXMgPSBjKCJweGxfY29sX2luX2Z1bGxyZXMiLCAicHhsX3Jvd19pbl9mdWxscmVzIiksCiAgICBzYW1wbGVfaWQgPSBzYW1wbGVfbmFtZSkKfQpgYGAKCgpgYGB7cn0Kc3BhY2VyYW5nZXJfb25seV9zcGUgPC0gZnVuY3Rpb24oc3BhY2VyYW5nZXJfb3V0c19kaXIsIHNhbXBsZV9uYW1lKXsKICAjIHJlYWQgaW4gc3BhY2VyYW5nZXIgb3V0cHV0cyBkaXJlY3RseSB1c2luZyByZWFkMTBYVmlzaXVtCiAgc3BhY2VyYW5nZXJfc3BlIDwtIHJlYWQxMHhWaXNpdW0oc3BhY2VyYW5nZXJfb3V0c19kaXIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZV9pZCA9IHNhbXBsZV9uYW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gInNwYXJzZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSAicmF3IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW1hZ2VzID0gImxvd3JlcyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsb2FkID0gRkFMU0UpCn0KYGBgCgpMZXQncyB0ZXN0IHRoZXNlIGZ1bmN0aW9ucyBvdXQgYW5kIHJlYWQgaW4gdGhlIEFsZXZpbi1mcnkgYW5kIFNwYWNlcmFuZ2VyIG91dHB1dCBmb3Igb25lIHNhbXBsZS4gCgpgYGB7cn0KIyBnZXQgcGF0aCB0byBmcnkgb3V0cHV0IGRpcmVjdG9yeSAKZnJ5X2RpciA8LSBmaWxlLnBhdGgoczNfb3V0LCAiYWxldmluLWZyeS1rbmVlIiwgc2FtcGxlX2lkc1sxXSkKZnJ5X2RpciA8LSBwYXN0ZTAoZnJ5X2RpciwgIi1zcGxpY2VkX2ludHJvbl90eG9tZV9rMzEtc2FsaWduLWNyLWxpa2UtZW0ta25lZSIpCgojIHBhdGhzIHRvIHNwYXRpYWwgZm9sZGVycyAKY2VsbHJhbmdlcl9mb2xkZXJzIDwtIHBhc3RlMChzYW1wbGVfaWRzLCAiLWNkbmEtc3BhdGlhbCIpCnNwYXRpYWxfZGlyIDwtIGZpbGUucGF0aChzM19vdXQsICJjZWxscmFuZ2VyIiwgY2VsbHJhbmdlcl9mb2xkZXJzWzFdLCAib3V0cyIsICJzcGF0aWFsIikKYGBgCgoKYGBge3J9CiMgcmVhZCBpbiBjb21iaW5lZCBmcnkgYW5kIHNwYWNlcmFuZ2VyIHNwZSAKZnJ5X3NwZSA8LSBjb252ZXJ0X2ZyeV9zcGFjZXJhbmdlcl9zcGUoZnJ5X2RpciwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwYXRpYWxfZGlyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX2lkc1sxXSkKYGBgCgpCZWxvdyB3ZSBpbXBvcnQganVzdCB0aGUgU3BhY2VyYW5nZXIgb3V0cHV0cyBpbnRvIGEgYFNwYXRpYWxFeHBlcmltZW50YCBvYmplY3QuCgpgYGB7cn0KIyBzcGFjZXJhbmdlciBvdXRwdXQgcGF0aHMKc3BhY2VyYW5nZXJfb3V0IDwtIGZpbGUucGF0aChzM19vdXQsICJjZWxscmFuZ2VyIiwgY2VsbHJhbmdlcl9mb2xkZXJzWzFdLCAib3V0cyIpCgojIHJlYWQgaW4gc3BhY2VyYW5nZXIgb3V0cHV0IGRpcmVjdGx5IHVzaW5nIHJlYWQxMFhWaXNpdW0Kc3BhY2VyYW5nZXJfc3BlIDwtIHNwYWNlcmFuZ2VyX29ubHlfc3BlKHNwYWNlcmFuZ2VyX291dCwgc2FtcGxlX2lkc1sxXSkKYGBgCgojIyMgSW1wb3J0aW5nIGFzIFNldXJhdCBPYmplY3QKCkJlbG93IHdlIHdpbGwgaW1wb3J0IHRoZSBzYW1lIGRhdGEgYnV0IGFzIGEgYFNldXJhdGAgb2JqZWN0IGluc3RlYWQgb2YgYSBgU3BhdGlhbEV4cGVyaW1lbnRgIG9iamVjdC4gCgpgYGB7cn0KIyBmaXJzdCBjcmVhdGUgc2NlIGZyb20gQWxldmluIG91dHB1dApmcnlfc2NlIDwtIHNjcGNhVG9vbHM6OnJlYWRfYWxldmluKGZyeV9kaXIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXNhX21vZGUgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoaWNoX2NvdW50cyA9ICJzcGxpY2VkIikKCiMgY29udmVydCBzY2UgdG8gc2V1cmF0IG9iamVjdCAKZnJ5X3NldXJhdCA8LSBDcmVhdGVTZXVyYXRPYmplY3QoY291bnRzID0gY291bnRzKGZyeV9zY2UpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9qZWN0ID0gIlNQQVRJQUwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhc3NheSA9ICJTcGF0aWFsIikKCiMgcmVhZCBpbiBpbWFnZSBkYXRhIHVzaW5nIFNldXJhdCBhbmQgc3Vic2V0IHRvIGNvbW1vbiBjZWxscwppbWFnZV9kYXRhIDwtIFJlYWQxMFhfSW1hZ2Uoc3BhdGlhbF9kaXIpCnJvd25hbWVzKGltYWdlX2RhdGFAY29vcmRpbmF0ZXMpIDwtIGdzdWIoIi0xIiwiIiwgcm93bmFtZXMoaW1hZ2VfZGF0YUBjb29yZGluYXRlcykpCmNvbW1vbl9jZWxscyA8LSBpbnRlcnNlY3QoQ2VsbHMoeCA9IGZyeV9zZXVyYXQpLCByb3duYW1lcyhpbWFnZV9kYXRhQGNvb3JkaW5hdGVzKSkKaW1hZ2VfZGF0YSA8LSBpbWFnZV9kYXRhW2NvbW1vbl9jZWxscyxdCgojIGFkZCBpbWFnZSBkYXRhIHRvIFNldXJhdApEZWZhdWx0QXNzYXkob2JqZWN0ID0gaW1hZ2VfZGF0YSkgPC0gIlNwYXRpYWwiCmZyeV9zZXVyYXRbWydzbGljZSddXSA8LSBpbWFnZV9kYXRhCmBgYAoKYGBge3J9CiMgbG9hZCBzcGFjZXJhbmdlciBvdXRwdXRzIGRpcmVjdGx5IGludG8gU2V1cmF0IG9iamVjdCAKc3BhY2VyYW5nZXJfc2V1cmF0IDwtIExvYWQxMFhfU3BhdGlhbChzcGFjZXJhbmdlcl9vdXQpCmBgYAoKCiMjIyBDb21wYXJpbmcgU2V1cmF0IGFuZCBTcGF0aWFsIEV4cGVyaW1lbnQKCk5vdyBsZXQncyB0YWtlIGEgbG9vayBhdCBob3cgZWFjaCBvZiB0aGVzZSBvYmplY3RzIHN0b3JlcyBpbmZvcm1hdGlvbiBmb3IgdGhlIGV4cGVyaW1lbnQuIApXZSB3aWxsIGp1c3QgbG9vayBhdCB0aGUgZmlsZXMgdGhhdCBoYXZlIGJlZW4gcHJvZHVjZWQgZnJvbSBTcGFjZXJhbmdlciBvbmx5IGFuZCBjb21wYXJlIHRoZSBgU2V1cmF0YCBvYmplY3QgdG8gdGhlIGBTcGF0aWFsRXhwZXJpbWVudGAuIAoKYGBge3J9CmhlYWQoY29sRGF0YShzcGFjZXJhbmdlcl9zcGUpKQpoZWFkKHNwYWNlcmFuZ2VyX3NldXJhdEBtZXRhLmRhdGEpCmBgYAoKU2V1cmF0IGF1dG9tYXRpY2FsbHkgYWRkcyBzb21lIHBlciBjZWxsIFFDIG1ldHJpY3MgaGVyZSwgYnV0IGluIGdlbmVyYWwgdGhlIGBjb2xEYXRhYCBsb29rcyBzaW1pbGFyIHRvIHdoYXQgd2Ugd291bGQgZXhwZWN0IGZvciBhIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgZm9yIHRoZSBgU3BhdGlhbEV4cGVyaW1lbnRgLgoKYGBge3J9CmhlYWQocm93RGF0YShzcGFjZXJhbmdlcl9zcGUpKQpoZWFkKHNwYWNlcmFuZ2VyX3NldXJhdFtbIlNwYXRpYWwiXV1AbWV0YS5mZWF0dXJlcykKYGBgCgpgYGB7cn0Kc3BhY2VyYW5nZXJfc3BlIDwtIHNwYWNlcmFuZ2VyX3NwZSAlPiUKICBzY3V0dGxlOjphZGRQZXJDZWxsUUNNZXRyaWNzKHN1YnNldHMgPSBsaXN0KG1pdG9fZ2VuZXMgPSBtaXRvX2dlbmVzW21pdG9fZ2VuZXMgJWluJSByb3duYW1lcyhzcGFjZXJhbmdlcl9zcGUpXSkpIApoZWFkKGNvbERhdGEoc3BhY2VyYW5nZXJfc3BlKSkKYGBgCgpZb3UgY2FuIHN0aWxsIHVzZSB0aGUgc2FtZSBmdW5jdGlvbnMgdG8gYWRkIGluIHBlciBjZWxsIG1ldHJpY3MgdGhhdCB3ZSBhcmUgdXNpbmcgaW4gYHNjcGNhVG9vbHNgIG5vdyBvbiBhIGBTcGF0aWFsRXhwZXJpbWVudGAuIAoKTGV0J3Mgc3BlY2lmaWNhbGx5IGxvb2sgYXQgaG93IGVhY2ggb2YgdGhlbSBzdG9yZXMgdGhlIGRhdGEgcmVsYXRlZCB0byB0aGUgc3BhdGlhbCBpbmZvcm1hdGlvbi4KVGhlIGBTcGF0aWFsRXhwZXJpbWVudGAgaGFzIHRocmVlIHBsYWNlcyB3aGVyZSBpdCBzdG9yZXMgc3BhdGlhbCBpbmZvcm1hdGlvbiwgYHNwYXRpYWxEYXRhYCwgYHNwYXRpYWxDb29yZHNgLCBhbmQgYGltZ0RhdGFgLiAKCmBgYHtyfQpoZWFkKHNwYXRpYWxEYXRhKHNwYWNlcmFuZ2VyX3NwZSkpCmBgYAoKYGBge3J9CmhlYWQoc3BhdGlhbENvb3JkcyhzcGFjZXJhbmdlcl9zcGUpKQpgYGAKCmBzcGF0aWFsRGF0YWAgaG9sZHMgaW5mb3JtYXRpb24gYWJvdXQgd2hldGhlciBvciBub3QgZWFjaCBiYXJjb2RlIHdhcyBjb3ZlcmVkIGJ5IHRpc3N1ZSBhbmQgc3BhdGlhbCBtZXRhZGF0YS4KYHNwYXRpYWxDb29yZHNgIGhvbGRzIHRoZSBjb29yZGluYXRlcyBmb3IgZWFjaCBzcG90LgoKVGhpcyBzYW1lIGluZm9ybWF0aW9uIGNhbiBiZSBmb3VuZCBieSBsb29raW5nIGluIHRoZSBgaW1hZ2VzYCBzbG90IGluIHRoZSBgU2V1cmF0YCBvYmplY3QuIAoKYGBge3J9CmhlYWQoc3BhY2VyYW5nZXJfc2V1cmF0QGltYWdlcyRzbGljZTFAY29vcmRpbmF0ZXMpCmBgYApgaW1nRGF0YWAgaG9sZHMgaW5mb3JtYXRpb24gYWJvdXQgZWFjaCBpbWFnZSB0aGF0IGlzIHN0b3JlZCBhcyBtdWx0aXBsZSBzbGljZXMgb2YgYW4gaW1hZ2UgY2FuIGJlIHN0b3JlZCB3aXRoaW4gb25lIGBTcGF0aWFsRXhwZXJpbWVudGAuIAoKYGBge3J9CmltZ0RhdGEoc3BhY2VyYW5nZXJfc3BlKQpgYGAKClRoaXMgc2FtZSBpbmZvcm1hdGlvbiBjYW4gYmUgZm91bmQgYnkgbG9va2luZyBhdCB0aGUgYGltYWdlc2Agc2xvdCBpbiB0aGUgYFNldXJhdGAgb2JqZWN0LiAKCmBgYHtyfQpzcGFjZXJhbmdlcl9zZXVyYXRAaW1hZ2VzJHNsaWNlMUBzY2FsZS5mYWN0b3JzCmBgYAoKTGV0J3MgdGFrZSBhIGxvb2sgYXQgc29tZSBleGFtcGxlIHBsb3RzIHRoYXQgY2FuIGJlIHByb2R1Y2VkIHdpdGggZWFjaCBvZiB0aGVzZSBvYmplY3RzLgoKYGBge3J9CmxpYnJhcnkodmlyaWRpcykKIyBwbG90IHRpc3N1ZSBzcG90cyBjb2xvcmluZyBieSB0b3RhbCBVTUkgY291bnQKcGxvdFNwb3RzKHNwZSA9IHNwYWNlcmFuZ2VyX3NwZSwgCiAgICAgICAgICB4X2Nvb3JkID0gInB4bF9jb2xfaW5fZnVsbHJlcyIsIAogICAgICAgICAgeV9jb29yZCA9ICJweGxfcm93X2luX2Z1bGxyZXMiLCAKICAgICAgICAgIGFubm90YXRlID0gInN1bSIpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2MoKQpgYGAKYGBge3J9ClNwYXRpYWxGZWF0dXJlUGxvdChzcGFjZXJhbmdlcl9zZXVyYXQsIGZlYXR1cmVzID0gIm5Db3VudF9TcGF0aWFsIikKYGBgCkl0IGxvb2tzIGxpa2Ugb3ZlcmFsbCB0aGV5IGJvdGggd291bGQgd29yayBhbmQgdGhhdCB0aGUgYFNwYXRpYWxFeHBlcmltZW50YCB3aWxsIHByb3ZpZGUgc2ltaWxhciBmdW5jdGlvbmFsaXR5IHRvIGhvdyB3ZSBoYXZlIHRoaW5ncyBhbHJlYWR5IHNldCB1cCBmb3Igc2luZ2xlLWNlbGwgZXhwZXJpbWVudHMuIApJIHRoaW5rIEkgaGF2ZSBhIHNsaWdodCBwcmVmZXJlbmNlIGZvciB0aGUgU2V1cmF0IHBsb3RzIGFzIHlvdSBjYW4gYWRqdXN0IHRoZSB0cmFuc3BhcmVuY3kgb2YgdGhlIHBvaW50cyBhbmQgYWxzbyBzZWUgdGhlIHRpc3N1ZSwgYnV0IEkgYWxzbyB0aGluayB3ZSBtYXkgYmUgYWJsZSB0byBwbGF5IGFyb3VuZCB3aXRoIHRoZSBwbG90cyB3aXRoIHRoZSBgU3BhdGlhbEV4cGVyaW1lbnRgIG1vcmUgaWYgd2Ugd2FudCBzb21ldGhpbmcgc2ltaWxhciB0byB0aGF0LiAKCiMjIEFsZXZpbi1mcnkgKyBTcGFjZXJhbmdlciB2ZXJzdXMgU3BhY2VyYW5nZXIgT25seQoKTm93IGxldCdzIHRha2UgYSBsb29rIGF0IGNvbXBhcmluZyB0aGUgdHdvIG1ldGhvZHMgb2YgdXNpbmcgQWxldmluLWZyeSArIFNwYWNlcmFuZ2VyIHRvIG9ubHkgU3BhY2VyYW5nZXIgZm9yIHF1YW50aWZpY2F0aW9uLiAKVG8gZG8gdGhpcywgd2Ugd2lsbCByZWFkIGluIGEgbGlzdCBvZiBBbGV2aW4tZnJ5ICsgU3BhY2VyYW5nZXIgYFNwYXRpYWxFeHBlcmltZW50YCBvYmplY3RzIGFuZCBTcGFjZXJhbmdlciBvYmplY3RzIGFuZCB0aGVuIG1lcmdlIHRoZW0gaW50byBvbmUgbGlzdCBiZWZvcmUgZ3JhYmJpbmcgdGhlIHBlciBjZWxsIGFuZCBwZXIgZ2VuZSBxdWFsaXR5IG1ldHJpY3MuIAoKIyMjIENyZWF0ZSBTcGF0aWFsIEV4cGVyaW1lbnRzIAoKYGBge3J9CiMgY3JlYXRlIHNhbXBsZSBpbmZvIGRhdGFmcmFtZSB0byBiZSBqb2luZWQgd2l0aCBwZXIgY2VsbCBkYXRhZnJhbWUgbGF0ZXIKc2FtcGxlX2luZm9fZGYgPC0gcXVhbnRfaW5mb190YWJsZShkYXRhX2Rpcj0gczNfb3V0LCAKICAgICAgICAgICAgICAgICB0b29scyA9IGMoImNlbGxyYW5nZXIiLCAiYWxldmluLWZyeS1rbmVlIiksCiAgICAgICAgICAgICAgICAgc2FtcGxlcyA9IHNhbXBsZV9pZHMpICU+JQogICMgY29udmVydCBjZWxscmFuZ2VyIHRvIHNwYWNlcmFuZ2VyIAogIGRwbHlyOjptdXRhdGUodG9vbCA9IGlmZWxzZSh0b29sID09ICJjZWxscmFuZ2VyIiwgInNwYWNlcmFuZ2VyIiwgdG9vbCkpCmBgYAoKYGBge3J9CiMgbW9kaWZ5IHNhbXBsZSBkYXRhIGZyYW1lIHRvIGdldCBzcGF0aWFsIGFuZCBmcnkgZGlyZWN0b3JpZXMgZm9yIGVhY2ggc2FtcGxlCmlucHV0X2RmIDwtIHNhbXBsZV9pbmZvX2RmICU+JQogIGRwbHlyOjpzZWxlY3QodG9vbCwgcXVhbnRfZGlyLCBzYW1wbGUsIGRhdGFfZGlyKSAlPiUKICBkcGx5cjo6bXV0YXRlKGRhdGFfZGlyID0gZHBseXI6OmNhc2Vfd2hlbih0b29sID09ICJzcGFjZXJhbmdlciIgfiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxlLnBhdGgoZGF0YV9kaXIsICJvdXRzIiwgInNwYXRpYWwiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b29sID09ICJhbGV2aW4tZnJ5IiB+IGRhdGFfZGlyKSkgJT4lCiAgdGlkeXI6OnBpdm90X3dpZGVyKGlkX2NvbHMgPSBzYW1wbGUsIG5hbWVzX2Zyb20gPSB0b29sLCB2YWx1ZXNfZnJvbSA9IGRhdGFfZGlyKQoKIyB1c2UgY3VzdG9tZSBmdW5jdGlvbiB0byBjcmVhdGUgbGlzdCBvZiBzcGUncwpmcnlfc3BlX2xpc3QgPC0gbWFwcGx5KGNvbnZlcnRfZnJ5X3NwYWNlcmFuZ2VyX3NwZSwKICAgICAgICAgICAgICAgICAgICAgICBpbnB1dF9kZiRgYWxldmluLWZyeWAsCiAgICAgICAgICAgICAgICAgICAgICAgaW5wdXRfZGYkc3BhY2VyYW5nZXIsCiAgICAgICAgICAgICAgICAgICAgICAgaW5wdXRfZGYkc2FtcGxlKQpuYW1lcyhmcnlfc3BlX2xpc3QpIDwtIHBhc3RlMChzYW1wbGVfaWRzLCAiLWZyeS1zcGFjZXJhbmdlci1jb21iaW5lZCIpCmBgYAoKCgpgYGB7cn0KIyBjcmVhdGUgbGlzdCBvZiBzcGFjZXJhbmdlciBvbmx5IHByb2Nlc3NlZCBzcGUncyAKIyBzcGFjZXJhbmdlciBvdXRwdXQgcGF0aHMKc3BhY2VyYW5nZXJfb3V0cyA8LSBmaWxlLnBhdGgoczNfb3V0LCAiY2VsbHJhbmdlciIsIGNlbGxyYW5nZXJfZm9sZGVycywgIm91dHMiKQpuYW1lcyhzcGFjZXJhbmdlcl9vdXRzKSA8LSBzYW1wbGVfaWRzCgpzcGFjZXJhbmdlcl9zcGVfbGlzdCA8LSBwdXJycjo6aW1hcChzcGFjZXJhbmdlcl9vdXRzLCBzcGFjZXJhbmdlcl9vbmx5X3NwZSkKbmFtZXMoc3BhY2VyYW5nZXJfc3BlX2xpc3QpIDwtIHBhc3RlMChzYW1wbGVfaWRzLCAiLXNwYWNlcmFuZ2VyLW9ubHkiKQpgYGAKCmBgYHtyfQphbGxfc3BlX2xpc3QgPC0gYXBwZW5kKGZyeV9zcGVfbGlzdCwgc3BhY2VyYW5nZXJfc3BlX2xpc3QpCgojIGNhbGN1bGF0ZSBwZXIgY2VsbCBRQyBhbmQgb3V0cHV0IHRvIGEgY29tYmluZWQgZGF0YSBmcmFtZSB3aXRoIHBsb3R0aW5nIAphbGxfc3BlX2xpc3QgPC0gYWxsX3NwZV9saXN0ICU+JQogICAgcHVycnI6Om1hcCgKICAgICAgfiBzY3V0dGxlOjphZGRQZXJDZWxsUUNNZXRyaWNzKC54LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1YnNldHMgPSBsaXN0KG1pdG8gPSBtaXRvX2dlbmVzW21pdG9fZ2VuZXMgJWluJSByb3duYW1lcygueCldKSkpICU+JQogIHB1cnJyOjptYXAoc2N1dHRsZTo6YWRkUGVyRmVhdHVyZVFDTWV0cmljcykKYGBgCgojIyMgUGVyIFNwb3QgUUMgTWV0cmljcwoKYGBge3J9CiMgY3JlYXRlIGN1c3RvbSBmdW5jdGlvbiB0byBncmFiIGNvbERhdGEgYW5kIGNvbnZlcnQgdG8gZGF0YSBmcmFtZSAKc3BhdGlhbF9jb2xkYXRhX3RvX2RmIDwtIGZ1bmN0aW9uKHNwZSl7CiAgZGYgPC0gYXMuZGF0YS5mcmFtZShjb2xEYXRhKHNwZSkpICU+JQogICAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4odmFyID0gInNwb3RfaWQiKQp9CmBgYAoKCmBgYHtyfQpmcnlfc2FtcGxlcyA9IGMoIlNDUENSMDAwMzcyLWZyeS1zcGFjZXJhbmdlci1jb21iaW5lZCIsICJTQ1BDUjAwMDM3My1mcnktc3BhY2VyYW5nZXItY29tYmluZWQiKQpzcGFjZXJhbmdlcl9zYW1wbGVzID0gYygiU0NQQ1IwMDAzNzItc3BhY2VyYW5nZXItb25seSIsICJTQ1BDUjAwMDM3My1zcGFjZXJhbmdlci1vbmx5IikKCiMgam9pbiBjb2xkYXRhIGRhdGFmcmFtZSB3aXRoIHNhbXBsZSBpbmZvCmNvbGRhdGFfZGYgPC0gcHVycnI6Om1hcF9kZihhbGxfc3BlX2xpc3Qsc3BhdGlhbF9jb2xkYXRhX3RvX2RmLCAuaWQgPSAic2FtcGxlIikgJT4lCiAgZHBseXI6Om11dGF0ZSh0b29sID0gZHBseXI6OmNhc2Vfd2hlbihzYW1wbGUgJWluJSBmcnlfc2FtcGxlcyB+ICJhbGV2aW4tZnJ5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZSAlaW4lIHNwYWNlcmFuZ2VyX3NhbXBsZXMgfiAic3BhY2VyYW5nZXIiKSwKICAgICAgICAgICAgICAgICMgZXh0cmFjdCBzYW1wbGUgbmFtZSBvbmx5IHRvIGFsbG93IGZvciBqb2luaW5nCiAgICAgICAgICAgICAgICBzYW1wbGUgPSBzdHJpbmdyOjp3b3JkKHNhbXBsZSwgMSwgc2VwID0gIi0iKSwKICAgICAgICAgICAgICAgICMgcmVtb3ZlIGV4dHJhIC0xIGZyb20gc3BhY2VyYW5nZXIgYmFyY29kZXMKICAgICAgICAgICAgICAgIHNwb3RfaWQgPSBnc3ViKCItMSIsICIiLCBzcG90X2lkKSklPiUKICBkcGx5cjo6bGVmdF9qb2luKHNhbXBsZV9pbmZvX2RmLAogICAgICAgICAgICAgICAgICAgYnkgPSBjKCJzYW1wbGUiLCAidG9vbCIpKQpgYGAKCmBgYHtyfQojIGlkZW50aWZ5IHNoYXJlZCBzcG90cyBvbmx5IApzcG90X2NvdW50cyA8LSBjb2xkYXRhX2RmICU+JSAgCiAgZHBseXI6OmNvdW50KHNwb3RfaWQsIHNhbXBsZSkKCmNvbW1vbl9zcG90cyA8LSBzcG90X2NvdW50cyAlPiUKICBkcGx5cjo6ZmlsdGVyKG4gPT0gMikgJT4lCiAgZHBseXI6OnB1bGwoc3BvdF9pZCkKCmNvbGRhdGFfZGZfY29tbW9uIDwtIGNvbGRhdGFfZGYgJT4lCiAgZHBseXI6OmZpbHRlcihzcG90X2lkICVpbiUgY29tbW9uX3Nwb3RzLAogICAgICAgICAgICAgICAgaW5fdGlzc3VlID09IDEpCmBgYAoKSW4gYWRkaXRpb24gdG8gZmlsdGVyaW5nIG9uIHNoYXJlZCBzcG90cyB3ZSB3aWxsIGFsc28gd2FudCB0byBmaWx0ZXIgYmFzZWQgb24gc3BvdHMgdGhhdCBhcmUgZm91bmQgdG8gb3ZlcmxhcCB3aXRoIHRpc3N1ZS4gCgpgYGB7cn0KIyBhbHNvIHdhbnQgdG8gZmlsdGVyIHRoZSBzcGUncyBkaXJlY3RseSAKZmlsdGVyX3NwZSA8LSBmdW5jdGlvbihzcGUpewogIHNwZSA8LSBzcGVbLCBzcGF0aWFsRGF0YShzcGUpJGluX3Rpc3N1ZSA9PSAxXQp9CgphbGxfc3Blc19maWx0ZXIgPC0gYWxsX3NwZV9saXN0ICU+JQogIHB1cnJyOjptYXAoZmlsdGVyX3NwZSkKYGBgCgoKYGBge3J9CiMgY3VzdG9tIGZ1bmN0aW9uIGZvciBwbG90dGluZyBzcGUgcmVzdWx0cyBhbmQgY29sb3JpbmcgYnkgY29sdW1uIG9mIGNvbERhdGEgb2YgY2hvaWNlCnBsb3Rfc3BlIDwtIGZ1bmN0aW9uKHNwZSwgc2FtcGxlLCBjb2x1bW4pewogIHBsb3RTcG90cyhzcGUsIAogICAgICAgICAgICB4X2Nvb3JkID0gInB4bF9jb2xfaW5fZnVsbHJlcyIsIAogICAgICAgICAgICB5X2Nvb3JkID0gInB4bF9yb3dfaW5fZnVsbHJlcyIsIAogICAgICAgICAgICBhbm5vdGF0ZSA9IGNvbHVtbikgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygpICsgCiAgICBnZ3RpdGxlKHNhbXBsZSkKfQpgYGAKCkZpcnN0IHdlIHdpbGwgbG9vayBhdCB0aGUgcGVyIGNlbGwgbWV0cmljczogbWl0b2Nob25kcmlhbCByZWFkcyBwZXIgY2VsbCwgdG90YWwgVU1JIHBlciBjZWxsLCBhbmQgdG90YWwgZ2VuZXMgZGV0ZWN0ZWQgcGVyIGNlbGwuIAoKYGBge3J9CiMgJSBtaXRvY2hvbmRyaWFsIHJlYWRzLyBzcG90IApnZ3Bsb3QoY29sZGF0YV9kZl9jb21tb24sIGFlcyh4ID0gdG9vbCwgeSA9IHN1YnNldHNfbWl0b19wZXJjZW50LCBmaWxsID0gdG9vbCkpICsgCiAgZ2VvbV9ib3hwbG90KCkgKyAKICBmYWNldF9ncmlkKH4gc2FtcGxlKSArCiAgdGhlbWVfY2xhc3NpYygpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKyAKICB5bGFiKCJNaXRvIFBlcmNlbnQiKSArIAogIHhsYWIoIiIpCmBgYApgYGB7cn0KYWxsX3NwZXNfZmlsdGVyICU+JQogIHB1cnJyOjppbWFwKHBsb3Rfc3BlLCAic3Vic2V0c19taXRvX3BlcmNlbnQiKQpgYGAKCk92ZXJhbGwgaXQgbG9va3MgbGlrZSBtaXRvY2hvbmRyaWFsIGNvbnRlbnQgaXMgbG93IGFuZCBmYWlybHkgc2ltaWxhciBhY3Jvc3MgYm90aCB0b29scy4gCgoKYGBge3J9CiMgdG90YWwgVU1JLyBzcG90IApnZ3Bsb3QoY29sZGF0YV9kZl9jb21tb24sIGFlcyh4ID0gc3VtLCBjb2xvciA9IHRvb2wpKSArIAogIGdlb21fZGVuc2l0eSgpICsgCiAgZmFjZXRfZ3JpZCh+IHNhbXBsZSkgKwogIHRoZW1lX2NsYXNzaWMoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsgCiAgeWxhYigiVU1JL3Nwb3QiKSArIAogIHhsYWIoIiIpCmBgYApgYGB7cn0KYWxsX3NwZXNfZmlsdGVyICU+JQogIHB1cnJyOjppbWFwKHBsb3Rfc3BlLCAic3VtIikKYGBgCgpJbnRlcmVzdGluZ2x5IHdoZW4gbG9va2luZyBhdCB0aGUgcGljdHVyZXMgKGJlc2lkZXMgdGhhdCB0aGUgQWxldmluLWZyeSBhbmQgU3BhY2VyYW5nZXIgcGljdHVyZXMgYXJlIGZsaXBwZWQpLCB0aGVyZSBhcHBlYXJzIHRvIGJlIGRpZmZlcmVudCBwYXR0ZXJucyBvZiB0b3RhbCBVTUkgY291bnQgcGVyIHNwb3Qgc2hvd2luZyB1cCBiZXR3ZWVuIGZyeSBhbmQgU3BhY2VyYW5nZXIuIAoKYGBge3J9CiMgdG90YWwgZ2VuZXMvIHNwb3QgCmdncGxvdChjb2xkYXRhX2RmX2NvbW1vbiwgYWVzKHggPSBkZXRlY3RlZCwgY29sb3IgPSB0b29sKSkgKyAKICBnZW9tX2RlbnNpdHkoKSArIAogIGZhY2V0X2dyaWQofiBzYW1wbGUpICsKICB0aGVtZV9jbGFzc2ljKCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArIAogIHlsYWIoIkdlbmVzIGRldGVjdGVkL3Nwb3QiKSArIAogIHhsYWIoIiIpIApgYGAKYGBge3J9CmFsbF9zcGVzX2ZpbHRlciAlPiUKICBwdXJycjo6aW1hcChwbG90X3NwZSwgImRldGVjdGVkIikKYGBgCkdlbmVyYWxseSBpdCBsb29rcyBsaWtlIGJvdGggdG9vbHMgYXJlIGZhaXJseSBzaW1pbGFyIGluIHRoZSBudW1iZXIgb2YgVU1JL2NlbGwgYW5kIGdlbmVzIGRldGVjdGVkL2NlbGwgYnV0IHRoZSBhY3R1YWwgc3BhdGlhbCBkaXN0cmlidXRpb24gb2YgbG9va3MgdmVyeSBkaWZmZXJlbnQgd2l0aGluIGVhY2ggc2FtcGxlIGFjcm9zcyBib3RoIHRvb2xzIHdoaWNoIGlzIGNvbmNlcm5pbmcuIAoKIyMjIFBlciBHZW5lIFFDIE1ldHJpY3MKCkxldCdzIGFsc28gbG9vayBhdCB0aGUgY29ycmVsYXRpb24gb2YgbWVhbiBnZW5lIGV4cHJlc3Npb24gYWNyb3NzIHNoYXJlZCBnZW5lcy4gCgpgYGB7cn0KIyBjdXN0b20gZnVuY3Rpb24gdG8gZ3JhYiByb3cgZGF0YSBmcm9tIHNwZQpzcGF0aWFsX3Jvd2RhdGFfdG9fZGYgPC0gZnVuY3Rpb24oc3BlKXsKICBkZiA8LSBhcy5kYXRhLmZyYW1lKHJvd0RhdGEoc3BlKSkgJT4lCiAgICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbih2YXIgPSAiZ2VuZV9pZCIpCn0KYGBgCgpgYGB7cn0KIyBjb21iaW5lIHJvd2RhdGEgd2l0aCBzYW1wbGUgaW5mbwpyb3dkYXRhX2RmIDwtIHB1cnJyOjptYXBfZGYoYWxsX3NwZV9saXN0LHNwYXRpYWxfcm93ZGF0YV90b19kZiwgLmlkID0gInNhbXBsZSIpICU+JQogIGRwbHlyOjptdXRhdGUodG9vbCA9IGRwbHlyOjpjYXNlX3doZW4oc2FtcGxlICVpbiUgZnJ5X3NhbXBsZXMgfiAiYWxldmluLWZyeSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGUgJWluJSBzcGFjZXJhbmdlcl9zYW1wbGVzIH4gInNwYWNlcmFuZ2VyIiksCiAgICAgICAgICAgICAgICBzYW1wbGUgPSBzdHJpbmdyOjp3b3JkKHNhbXBsZSwgMSwgc2VwID0gIi0iKSkgJT4lCiAgZHBseXI6OmxlZnRfam9pbihzYW1wbGVfaW5mb19kZiwKICAgICAgICAgICAgICAgICAgIGJ5ID0gYygic2FtcGxlIiwgInRvb2wiKSkKCgpnZW5lX2NvdW50cyA8LSByb3dkYXRhX2RmICU+JSAKICAjIHJlbW92ZSBnZW5lcyB0aGF0IGhhdmUgYSBsb3cgZnJlcXVlbmN5IG9mIGJlaW5nIGRldGVjdGVkCiAgZHBseXI6OmZpbHRlcihkZXRlY3RlZCA+PSA1LjApICU+JQogIGRwbHlyOjpjb3VudChnZW5lX2lkLCBzYW1wbGUpCgojIHJlc3RyaWN0IHRvIG9ubHkgY29tbW9uIGdlbmVzIApjb21tb25fZ2VuZXMgPC0gZ2VuZV9jb3VudHMgJT4lCiAgZHBseXI6OmZpbHRlcihuID09IDIpICU+JQogIGRwbHlyOjpwdWxsKGdlbmVfaWQpCgpyb3dkYXRhX2RmX2NvbW1vbiA8LSByb3dkYXRhX2RmICU+JQogIGRwbHlyOjpmaWx0ZXIoCiAgICAoZ2VuZV9pZCAlaW4lIGNvbW1vbl9nZW5lcykgCiAgKQpgYGAKCmBgYHtyfQpyb3dkYXRhX2NvciA8LSByb3dkYXRhX2RmX2NvbW1vbiAlPiUKICBkcGx5cjo6c2VsZWN0KHRvb2wsIGdlbmVfaWQsIHNhbXBsZSwgbWVhbikgJT4lCiAgIyBzcHJlYWQgdGhlIG1lYW4gZXhwcmVzc2lvbiBzdGF0cyB0byBvbmUgY29sdW1uIHBlciBjYWxsZXIKICB0aWR5cjo6cGl2b3Rfd2lkZXIoaWRfY29scyA9IGMoZ2VuZV9pZCwgc2FtcGxlKSwKICAgICAgICAgICAgICAgICAgICAgbmFtZXNfZnJvbSA9IGMoInRvb2wiKSwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzX2Zyb20gPSBtZWFuKSAlPiUKICAjIGRyb3Agcm93cyB3aXRoIE5BIHZhbHVlcyB0byBlYXNlIGNvcnJlbGF0aW9uIGNhbGN1bGF0aW9uCiAgdGlkeXI6OmRyb3BfbmEoKQpgYGAKCmBgYHtyfQojIGxvb2sgYXQgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgdHdvIHRvb2xzCnJvd2RhdGFfY29yICU+JSAKICBkcGx5cjo6Z3JvdXBfYnkoc2FtcGxlKSAlPiUKICBkcGx5cjo6c3VtbWFyaXplKAogICAgYWxldmluX2ZyeV9rbmVlX3NwYWNlcmFuZ2VyX2NvciA9IGNvcihgc3BhY2VyYW5nZXJgLCBgYWxldmluLWZyeWAsIG1ldGhvZCA9ICJzcGVhcm1hbiIpCiAgKQpgYGAKCmBgYHtyfQojIG1lYW4gZ2VuZSBleHByZXNzaW9uIGFjcm9zcyBzaGFyZWQgZ2VuZXMgCmdncGxvdChyb3dkYXRhX2NvciwgYWVzKHggPSBgc3BhY2VyYW5nZXJgLCB5ID0gYGFsZXZpbi1mcnlgKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDAuNSwgYWxwaGEgPSAwLjEpICsgCiAgZmFjZXRfd3JhcCh+IHNhbXBsZSkgKyAKICBzY2FsZV94X2xvZzEwKCkgKyAKICBzY2FsZV95X2xvZzEwKCkgKyAKICBsYWJzKHggPSAiU3BhY2VyYW5nZXIgbWVhbiBnZW5lIGV4cHJlc3Npb24iLCB5ID0gIkFsZXZpbiBGcnkgTWVhbiBnZW5lIGV4cHJlc3Npb24iKSArIAogIHRoZW1lX2NsYXNzaWMoKQpgYGAKIyMjIFNoYXJlZCBnZW5lcyBhY3Jvc3MgdG9vbHMKClRoZSBmaW5hbCB0aGluZyB3ZSB3aWxsIGxvb2sgYXQgaXMgdGhlIG92ZXJsYXAgb2YgZ2VuZXMgZGV0ZWN0ZWQgaW4gZWFjaCB0b29sLiAKRmlyc3Qgd2UgbmVlZCB0byBmaWx0ZXIgdGhlIGBTcGF0aWFsRXhwZXJpbWVudGAgb2JqZWN0cyB0byBvbmx5IGhhdmUgc3BvdHMgdGhhdCBhcmUgZm91bmQgaW4gYm90aCB0b29scyBhbmQgdGhlbiB3ZSBmaWx0ZXIgdG8gaW5jbHVkZSBvbmx5IGdlbmVzIHRoYXQgYXJlIGZvdW5kIGluIGJvdGggaW5kaWNlcyBiZWZvcmUgbG9va2luZyBhdCB0aGUgb3ZlcmxhcC4gCgpgYGB7cn0KIyBtYWtlIGEgZnVuY3Rpb24gdG8gZmlsdGVyIHNjZXMgYnkgc3Vic2V0IG9mIHNwb3RzIGFuZCByZS1jYWxjdWxhdGUgZmVhdHVyZSBzdGF0cwpmaWx0ZXJfc3BlIDwtIGZ1bmN0aW9uKHNwZSwgc3BvdHMpewogICMgcmVtb3ZlICItMSIgYXQgZW5kIG9mIGJhcmNvZGUgZm9yIHNwYWNlcmFuZ2VyIHNwZXMKICBjb2xuYW1lcyhzcGUpIDwtIGdzdWIoIi0xIiwgIiIsIGNvbG5hbWVzKHNwZSkpCiAgY2VsbHNfdG9fa2VlcCA8LSBjb2xuYW1lcyhzcGUpICVpbiUgc3BvdHMKICByb3dEYXRhKHNwZSkgPC0gTlVMTAogIHNwZVssIGNlbGxzX3RvX2tlZXBdICU+JQogICAgc2N1dHRsZTo6YWRkUGVyRmVhdHVyZVFDTWV0cmljcygpCn0KYGBgCgpgYGB7cn0KIyBmaWx0ZXIgYWxsIHNwZXMgdG8gb25seSBoYXZlIGNvbW1vbiBzcG90cyAKc3BlX2xpc3RfY29tbW9uIDwtIGFsbF9zcGVzX2ZpbHRlciAlPiUKICBwdXJycjo6bWFwKGZpbHRlcl9zcGUsIHNwb3RzID0gY29tbW9uX3Nwb3RzKQoKIyBncmFiIHJvd2RhdGEgZnJvbSBmaWx0ZXJlZCBzY2VzIApyb3dkYXRhX2RmX2ZpbHRlcmVkIDwtIHB1cnJyOjptYXBfZGYoc3BlX2xpc3RfY29tbW9uLCBzcGF0aWFsX3Jvd2RhdGFfdG9fZGYsIC5pZCA9ICJzYW1wbGUiKSAlPiUKICBkcGx5cjo6bXV0YXRlKHRvb2wgPSBkcGx5cjo6Y2FzZV93aGVuKHNhbXBsZSAlaW4lIGZyeV9zYW1wbGVzIH4gImFsZXZpbi1mcnkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlICVpbiUgc3BhY2VyYW5nZXJfc2FtcGxlcyB+ICJzcGFjZXJhbmdlciIpLAogICAgICAgICAgICAgICAgc2FtcGxlID0gc3RyaW5ncjo6d29yZChzYW1wbGUsIDEsIHNlcCA9ICItIikpICU+JQogIGRwbHlyOjpsZWZ0X2pvaW4oc2FtcGxlX2luZm9fZGYsCiAgICAgICAgICAgICAgICAgICBieSA9IGMoInNhbXBsZSIsICJ0b29sIikpCmBgYAoKCmBgYHtyfQojIGdldCBnZW5lcyBjb21tb24gaW4gYWxsIHRvb2xzCmNvbW1vbl9nZW5lcyA8LSByb3dkYXRhX2RmX2ZpbHRlcmVkICU+JQogIGRwbHlyOjpzZWxlY3QoZ2VuZV9pZCwgdG9vbCkgJT4lCiAgZHBseXI6OmRpc3RpbmN0KCkgJT4lCiAgZHBseXI6Omdyb3VwX2J5KGdlbmVfaWQpICU+JQogIGRwbHlyOjp0YWxseSgpICU+JQogICMgZmlsdGVyIGZvciBnZW5lcyBmb3VuZCBpbiBib3RoIHNwYWNlcmFuZ2VyIGFuZCBhbGV2aW4tZnJ5CiAgZHBseXI6OmZpbHRlcihuID09IDIpICU+JQogIGRwbHlyOjpwdWxsKGdlbmVfaWQpCgojIGZpbHRlciByb3dkYXRhX2RmIHRvIG9ubHkgaW5jbHVkZSBnZW5lcyBmb3VuZCBpbiBhbGwgdG9vbHMgYW5kIGdlbmVzIHdpdGggbWVhbiA+IDAgYW5kIGRldGVjdGVkID4gMCBpbiBhbGwgY2VsbHMKcm93ZGF0YV9kZl9maWx0ZXJlZCA8LSByb3dkYXRhX2RmX2ZpbHRlcmVkICU+JSAKICBkcGx5cjo6ZmlsdGVyKGdlbmVfaWQgJWluJSBjb21tb25fZ2VuZXMpICU+JQogIGRwbHlyOjpmaWx0ZXIobWVhbiA+IDAgJiBkZXRlY3RlZCA+IDApCmBgYAoKCmBgYHtyfQojIGNyZWF0ZSB1cHNldCBwbG90IApnZW5lX2RldGVjdF9kZiA8LSByb3dkYXRhX2RmX2ZpbHRlcmVkICU+JQogIGRwbHlyOjpncm91cF9ieShnZW5lX2lkKSAlPiUKICBkcGx5cjo6c3VtbWFyaXNlKHRvb2xzX2RldGVjdGVkID0gbGlzdCh1bmlxdWUodG9vbCkpKQoKZ2dwbG90KGdlbmVfZGV0ZWN0X2RmLCBhZXMoeCA9IHRvb2xzX2RldGVjdGVkKSkgKwogIGdlb21fYmFyKCkgKwogIHNjYWxlX3hfdXBzZXQobl9pbnRlcnNlY3Rpb25zID0gMykKYGBgCgpTaW1pbGFyIHRvIHdpdGggdGhlIHNpbmdsZS1jZWxsIGRhdGEgbW9zdCBnZW5lcyBhcmUgZGV0ZWN0ZWQgaW4gYm90aCBBbGV2aW4tZnJ5IGFuZCBTcGFjZXJhbmdlciB3aGljaCBpcyBnb29kLiAKCiMjIENvbmNsdWRpbmcgdGhvdWdodHMgCgotIEl0IGFwcGVhcnMgdGhhdCB3ZSBjYW4gdXNlIHRoZSBgU3BhdGlhbEV4cGVyaW1lbnRgIHRvIHN0b3JlIHRoZSBxdWFudGlmaWNhdGlvbiBvdXRwdXQgYW5kIGl0IHdpbGwgbWFpbnRhaW4gc2ltaWxhciBmdW5jdGlvbmFsaXR5IHRvIHRoZSBgU2luZ2xlQ2VsbEV4cGVyaW1lbnRgIG9iZWpjdHMgd2UgdXNlIGZvciBzaW5nbGUtY2VsbCBhbmQgc2luZ2xlLW51Y2xlaSBkYXRhLiAKLSBBbHRob3VnaCBBbGV2aW4tZnJ5IGFuZCBTcGFjZXJhbmdlciBvbmx5IHJlc3VsdCBpbiBzaW1pbGFyIGRpc3RyaWJ1dGlvbnMgb2YgVU1JL3Nwb3QgYW5kIGdlbmVzL3Nwb3QsIHRoZSBwYXR0ZXJuaW5nIGFjcm9zcyB0aGUgdGlzc3VlIGlzIG5vdCB0aGUgc2FtZSB3aGljaCBpcyBub3QgaWRlYWwgYW5kIGRvZXNuJ3QgcHJvdmlkZSBzdXBwb3J0IGZvciBhZGRpbmcgdGhlIHVzZSBvZiBBbGV2aW4tZnJ5IHRvIG91ciB3b3JrZmxvdyAoc2luY2Ugd2UgaGF2ZSB0byB1c2UgU3BhY2VyYW5nZXIgYW55d2F5cykuICAKCiMjIFNlc3Npb24gSW5mbwoKYGBge3J9CnNlc3Npb25pbmZvOjpzZXNzaW9uX2luZm8oKQpgYGAKCg==